package pathfinding

import (
	"math"
	"math/rand"

	"gddgame.cc/galaxy/utils"
)

const squareRT2 = 1.414213562

var (
	squareD = []Vector2{
		{0, 1},   //N 0
		{1, 1},   //NE 1
		{1, 0},   //E 2
		{1, -1},  //SE 3
		{0, -1},  //S 4
		{-1, -1}, //SW 5
		{-1, 0},  //W 6
		{-1, 1},  //NW 7
	}
)

type SquareInstance struct {
}

func NewSquareInstance() *SquareInstance {
	instance := &SquareInstance{}
	return instance
}
func (instance *SquareInstance) RandomPosition(position *Vector2, fn func(point *Vector2) bool) {

}
func (instance *SquareInstance) GetDirection(result *Vector2, x int16, y int16, dir byte) {
	result.X = squareD[dir].X
	result.Y = squareD[dir].Y
}
func (instance *SquareInstance) Direction(start *Vector2, end *Vector2) byte {
	dx := end.X - start.X
	dy := end.Y - start.Y
	for i := byte(0); i < 8; i++ {
		d := squareD[i]
		if d.X == dx && d.Y == dy {
			return i
		}
	}
	return 0
}

func (instance *SquareInstance) Distance(current *Vector2, goal *Vector2) float64 {
	return math.Sqrt(math.Pow(float64(current.X-goal.X), 2) + math.Pow(float64(current.Y-goal.Y), 2))
}

func (instance *SquareInstance) DiagonalCost() float64 {
	return squareRT2
}

func (instance *SquareInstance) IsDiagonal(dir byte) bool {
	return (dir % 2) != 0
}

func (instance *SquareInstance) ExtendDir(sourceDir byte, currentDir byte) []byte {
	if instance.IsDiagonal(currentDir) {
		return []byte{(currentDir + 1) % 8, (currentDir + 8 - 1) % 8}
	}
	return []byte{}
}

func (instance *SquareInstance) DirectionCount() byte {
	return 8
}

func (instance *SquareInstance) ForceNeighbours(jps *JPS, grid JPSGrid, node *JPSNode, mask PathMask, dirs byte) (bool, byte) {
	if node.Dir == 255 {
		return false, 255
	}
	flag := false
	if instance.IsDiagonal(node.Dir) {
		if jps.ENTERABLE(grid, node, node.Dir, 6, mask) && !jps.ENTERABLE(grid, node, node.Dir, 5, mask) {
			dirs = AddDirectionToSet(dirs, (node.Dir+6)%8)
			flag = true
		}
		if jps.ENTERABLE(grid, node, node.Dir, 2, mask) && !jps.ENTERABLE(grid, node, node.Dir, 3, mask) {
			dirs = AddDirectionToSet(dirs, (node.Dir+2)%8)
			flag = true
		}
	} else {
		if jps.ENTERABLE(grid, node, node.Dir, 7, mask) && !jps.ENTERABLE(grid, node, node.Dir, 6, mask) {
			dirs = AddDirectionToSet(dirs, (node.Dir+7)%8)
			flag = true
		}
		if jps.ENTERABLE(grid, node, node.Dir, 1, mask) && !jps.ENTERABLE(grid, node, node.Dir, 2, mask) {
			dirs = AddDirectionToSet(dirs, (node.Dir+1)%8)
			flag = true
		}
	}
	return flag, dirs
}

func (instance *SquareInstance) NaturalNeighbours(dir byte) byte {
	if dir == 255 {
		return 255
	}

	dirs := byte(0)
	dirs = AddDirectionToSet(dirs, dir)
	if instance.IsDiagonal(dir) {
		dirs = AddDirectionToSet(dirs, (dir+1)%8)
		dirs = AddDirectionToSet(dirs, (dir+8-1)%8)
	} else {
		dirs = AddDirectionToSet(dirs, (dir+2)%8)
		dirs = AddDirectionToSet(dirs, (dir+8-2)%8)
	}
	return dirs
}
func (instance *SquareInstance) GiveUpNeighbours(dir byte) byte {
	dirs := byte(0)
	dirs = AddDirectionToSet(dirs, (dir+3)%8)
	dirs = AddDirectionToSet(dirs, (dir+4)%8)
	dirs = AddDirectionToSet(dirs, (dir+5)%8)
	return dirs
}

type HexMode byte

const (
	_ HexMode = iota
	RowHexMode
	ColumnHexMode
)

type HexDirectionColumn byte

const (
	NEColumn HexDirectionColumn = iota
	EColumn
	SEColumn
	SWColumn
	WColumn
	NWColumn
)

type HexDirectionRow byte

const (
	NERow HexDirectionRow = iota
	SERow
	SRow
	SWRow
	NWRow
	NRow
)

type HexInstance struct {
	mode HexMode
}

func NewHexInstance(mode HexMode) *HexInstance {
	instance := &HexInstance{mode: mode}
	return instance
}
func (instance *HexInstance) RandomPosition(position *Vector2, distance int, fn func(point *Vector2) bool) {
	cube := position.EvenQToCube()
	flag := true
	r := 0
	for i := 0; i < distance; i++ {
		d := rand.Int31n(int32(distance)) + 1
		//fmt.Println("range", d)
		if utils.IncludeInt(r, uint(d)) {
			i -= 1
			continue
		}
		r |= 1 << (d - 1)
		dd := int(rand.Int31n(6))
		HexRingDistanceFunc(&cube, int(d), dd, func(c *Cube) bool {
			v := c.ToEvenQ()
			flag = fn(&v)
			return flag
		})
		if !flag {
			return
		}
	}
}
func (instance *HexInstance) RangePosition(position *Vector2, distance int, fn func(point *Vector2) bool) {
	cube := position.EvenQToCube()
	flag := true
	for i := 0; i < distance; i++ {
		d := i + 1
		dd := int(rand.Int31n(6))
		HexRingDistanceFunc(&cube, int(d), dd, func(c *Cube) bool {
			v := c.ToEvenQ()
			flag = fn(&v)
			return flag
		})
		if !flag {
			return
		}
	}
}
func (instance *HexInstance) CubeToVector(cube *Cube) Vector2 {
	return cube.ToEvenQ()
}

func (instance *HexInstance) VectorToCube(vector *Vector2) Cube {
	return vector.EvenQToCube()
}

func (instance *HexInstance) GetDirection(result *Vector2, x int16, y int16, dir byte) {
	if instance.mode == RowHexMode {
		instance.WithDirectionRow(x, y, HexDirectionRow(dir), result)
	} else {
		instance.WithDirectionColumn(x, y, HexDirectionColumn(dir), result)
	}
}
func (instance *HexInstance) Direction(start *Vector2, end *Vector2) byte {
	if instance.mode == RowHexMode {
		s := start.EvenQToCube()
		e := end.EvenQToCube()
		return ToDirection(&s, &e)
	} else {
		s := start.OddRToCube()
		e := end.OddRToCube()
		return ToDirection(&s, &e)
	}
}

func (instance *HexInstance) WithDirectionColumn(x int16, y int16, direction HexDirectionColumn, target *Vector2) {
	a := ODDR[y&1][direction]
	target.X = a[0]
	target.Y = a[1]
}

func (instance *HexInstance) WithDirectionRow(x int16, y int16, direction HexDirectionRow, target *Vector2) {
	a := EVENQ[x&1][direction]
	target.X = a[0]
	target.Y = a[1]
}

func (instance *HexInstance) Distance(current *Vector2, goal *Vector2) float64 {
	a := current.EvenQToCube()
	b := goal.EvenQToCube()
	return float64(DistanceWithCube(&a, &b))
}

func (instance *HexInstance) DiagonalCost() float64 {
	return 1
}

func (instance *HexInstance) ExtendDir(sourceDir byte, currentDir byte) []byte {
	if currentDir == sourceDir || sourceDir == 255 {
		return []byte{(currentDir + 1) % 6, (currentDir + 5) % 6}
	} else if currentDir > sourceDir || currentDir == 0 {
		return []byte{(currentDir + 1) % 6}
	} else {
		return []byte{(currentDir + 5) % 6}
	}
}

func (instance *HexInstance) DirectionCount() byte {
	return 6
}

func (instance *HexInstance) ForceNeighbours(jps *JPS, grid JPSGrid, node *JPSNode, mask PathMask, dirs byte) (bool, byte) {
	if node.Dir == 255 {
		return false, 255
	}
	flag := false
	if jps.ENTERABLE(grid, node, node.Dir, 5, mask) && !jps.ENTERABLE(grid, node, node.Dir, 4, mask) {
		dirs = AddDirectionToSet(dirs, (node.Dir+5)%6)
		flag = true
	}
	if jps.ENTERABLE(grid, node, node.Dir, 1, mask) && !jps.ENTERABLE(grid, node, node.Dir, 2, mask) {
		dirs = AddDirectionToSet(dirs, (node.Dir+1)%6)
		flag = true
	}
	//if jps.ENTERABLE(node, dir, 2, mask) && !jps.ENTERABLE(node, dir, 3, mask) {
	//	dirs = jps.AddDirectionToSet(dirs, (dir+2)%6)
	//	flag = true
	//}
	return flag, dirs
}

func (instance *HexInstance) NaturalNeighbours(dir byte) byte {
	if dir == 255 {
		return 255
	}

	dirs := byte(0)
	dirs = AddDirectionToSet(dirs, dir)
	d := (dir + 1) % 6
	dirs = AddDirectionToSet(dirs, d)
	//if instance.IsDiagonal(d) {
	//	dirs = jps.AddDirectionToSet(dirs, (dir+2)%6)
	//}

	d = (dir + 6 - 1) % 6
	dirs = AddDirectionToSet(dirs, d)
	//if instance.IsDiagonal(d) {
	//	dirs = jps.AddDirectionToSet(dirs, (dir+6-2)%6)
	//}
	return dirs
}

func (instance *HexInstance) GiveUpNeighbours(dir byte) byte {
	dirs := byte(0)
	dirs = AddDirectionToSet(dirs, (dir+2)%6)
	dirs = AddDirectionToSet(dirs, (dir+4)%6)
	return dirs
}
